package com.symone.webcms.service.impl;

import com.symone.webcms.model.ScheduleTask;
import com.symone.webcms.mapper.ScheduleTaskMapper;
import com.symone.webcms.schedule.TaskFactory;
import com.symone.webcms.schedule.TaskFactoryDisallowConcurrentExecution;
import com.symone.webcms.service.ScheduleTaskService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.symone.webcms.util.TaskUtil;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 *　　
 *   @description : ScheduleTask 服务实现类
 *   ---------------------------------
 * 	 @author Archer Chu
 *   @since 2019-04-04
 */
@Service
public class ScheduleTaskServiceImpl extends ServiceImpl<ScheduleTaskMapper, ScheduleTask> implements ScheduleTaskService {

    private static Logger logger = LoggerFactory.getLogger(ScheduleTaskService.class);

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @Override
    public Page<ScheduleTask> findPage(Page<ScheduleTask> page, Wrapper<ScheduleTask> scheduleTask) {
        List<ScheduleTask> result = baseMapper.selectPage(page,scheduleTask);
        page.setRecords(result);
        return page;
    }

    @Override
    public List<ScheduleTask> findAll() {
        Wrapper<ScheduleTask> ew = new EntityWrapper<ScheduleTask>();
        ew.where("1=1");
        return baseMapper.selectList(ew);
    }

    @Override
    public int delete(long id) {
        return baseMapper.deleteById(id);
    }

    @Override
    public boolean deleteFlagById(long id) {
        return baseMapper.deleteFlagById(id);
    }

    @Override
    public int clear() {
        return baseMapper.delete(new EntityWrapper<ScheduleTask>().where("1=0"));
    }

    @Override
    @Transactional
    public int updateTimeById(Date previousTime, Date nextTime, Long id) {
        return baseMapper.updateTimeById(previousTime,nextTime,id);
    }

    @Override
    @Transactional
    public int updateStatusById(String status, Long id) {
        return baseMapper.updateStatusById(status,id);
    }

    /**
     * 获取单个任务
     * @param jobName
     * @param jobGroup
     * @return
     * @throws SchedulerException
     */
    public ScheduleTask getJob(String jobName,String jobGroup) throws SchedulerException {
        ScheduleTask task = null;
        Scheduler scheduler = getScheduler();
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (null != trigger) {
            task = createJob(jobName, jobGroup, scheduler, trigger);
        }

        return task;
    }

    private ScheduleTask createJob(String jobName, String jobGroup, Scheduler scheduler, Trigger trigger)
            throws SchedulerException {
        ScheduleTask task;
        task = new ScheduleTask();
        task.setName(jobName);
        task.setGroup(jobGroup);
        task.setDescription("触发器:" + trigger.getKey());
        task.setNextTime(trigger.getNextFireTime());
        task.setPreviousTime(trigger.getPreviousFireTime());

        Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
        task.setStatus(triggerState.name());

        if(trigger instanceof CronTrigger) {
            CronTrigger cronTrigger = (CronTrigger)trigger;
            String cronExpression = cronTrigger.getCronExpression();
            task.setCron(cronExpression);
        }
        return task;
    }

    /**
     * 获取所有任务
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleTask> getAllJobs() throws SchedulerException{
        Scheduler scheduler = getScheduler();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        List<ScheduleTask> taskList = new ArrayList<ScheduleTask>();
        List<? extends Trigger> triggers;
        ScheduleTask task;
        for (JobKey jobKey : jobKeys) {
            triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers) {
                task = createJob(jobKey.getName(), jobKey.getGroup(), scheduler, trigger);
                taskList.add(task);
            }
        }

        return taskList;
    }

    /**
     * 所有正在运行的job
     *
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleTask> getRunningJob() throws SchedulerException {
        Scheduler scheduler = getScheduler();
        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
        List<ScheduleTask> taskList = new ArrayList<ScheduleTask>(executingJobs.size());
        ScheduleTask task;
        JobDetail jobDetail;
        JobKey jobKey;

        for (JobExecutionContext executingJob : executingJobs) {
            jobDetail = executingJob.getJobDetail();
            jobKey = jobDetail.getKey();

            task = createJob(jobKey.getName(), jobKey.getGroup(), scheduler, executingJob.getTrigger());
            taskList.add(task);
        }

        return taskList;
    }

    /**
     * 添加任务
     *
     * @param task
     * @throws SchedulerException
     */
    public boolean addJob(ScheduleTask task) throws SchedulerException {
        if(task == null || !ScheduleTask.STATUS_RUNNING.equals(task.getStatus())) {
            return false;
        }

        String jobName = task.getName();
        String jobGroup = task.getGroup();
        if(!TaskUtil.isValidExpression(task.getCron())) {
            logger.error("时间表达式错误（"+jobName+","+jobGroup+"）, "+task.getCron());
            return false;
        } else {
            Scheduler scheduler = getScheduler();
            // 任务名称和任务组设置规则：    // 名称：task_1 ..    // 组 ：group_1 ..
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName,  jobGroup);
            Trigger trigger = scheduler.getTrigger(triggerKey);
            // 不存在，创建一个
            if (null == trigger) {
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCron());
                // 按新的表达式构建一个新的trigger
                trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
                        .startAt(task.getStartTime()==null ? (new Date()) : task.getStartTime()) // 设置job不早于这个时间进行运行,和调用trigger的setStartTime方法效果一致
                        .withSchedule(scheduleBuilder).build();

                //是否允许并发执行
                JobDetail jobDetail = getJobDetail(task);
                // 将 task 信息存入数据库
                task.setStartTime(trigger.getStartTime());
                task.setNextTime(trigger.getNextFireTime());
                task.setPreviousTime(trigger.getPreviousFireTime());
                save(task);
                jobDetail.getJobDataMap().put(getJobIdentity(task), task);

                scheduler.scheduleJob(jobDetail, trigger);

            } else { // trigger已存在，则更新相应的定时设置
                // 更新 task 信息到数据库
                task.setStartTime(trigger.getStartTime());
                task.setNextTime(trigger.getNextFireTime());
                task.setPreviousTime(trigger.getPreviousFireTime());
                save(task);
                getJobDetail(task).getJobDataMap().put(getJobIdentity(task), task);

                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCron());
                // 按新的表达式构建一个新的trigger
                trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
                        .startAt(task.getStartTime()==null ? (new Date()) : task.getStartTime()) // 设置job不早于这个时间进行运行,和调用trigger的setStartTime方法效果一致
                        .withSchedule(scheduleBuilder).build();
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        }
        return true;
    }

    private String getJobIdentity(ScheduleTask task) {
        return "scheduleJob"+(task.getGroup() +"_"+task.getName());
    }

    private JobDetail getJobDetail(ScheduleTask task) {
        Class<? extends Job> clazz = ScheduleTask.CONCURRENT_IS.equals(task.getIsConcurrent())
                ? TaskFactory.class : TaskFactoryDisallowConcurrentExecution.class;
        JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(task.getName(), task.getGroup()).build();
        return jobDetail;
    }

    /**
     * 暂停任务
     * @param task
     * @return
     */
    @Transactional
    public boolean pauseJob(ScheduleTask task){
        Scheduler scheduler = getScheduler();
        JobKey jobKey = JobKey.jobKey(task.getName(), task.getGroup());
        boolean result;
        try {
            scheduler.pauseJob(jobKey);

            // 更新任务状态到数据库
            task.setStatus(ScheduleTask.STATUS_NOT_RUNNING);
            baseMapper.updateStatusById(task.getStatus(), task.getId());

            result = true;
        } catch (SchedulerException e) {
            result = false;
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 恢复任务
     * @param task
     * @return
     */
    @Transactional
    public boolean resumeJob(ScheduleTask task){
        Scheduler scheduler = getScheduler();
        JobKey jobKey = JobKey.jobKey(task.getName(), task.getGroup());
        boolean result;
        try {
            logger.info("resume job : " + (task.getGroup() + "_" + task.getName()));
            TriggerKey triggerKey = TriggerKey.triggerKey(task.getName(), task.getGroup());
            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCron());
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
                    .startAt(task.getStartTime()==null ? (new Date()) : task.getStartTime()) // 设置job不早于这个时间进行运行,和调用trigger的setStartTime方法效果一致
                    .withSchedule(scheduleBuilder).build();
            scheduler.rescheduleJob(triggerKey, trigger);
            scheduler.resumeJob(jobKey);

            // 更新任务状态到数据库
            task.setStatus(ScheduleTask.STATUS_RUNNING);
            baseMapper.updateStatusById(task.getStatus(), task.getId());

            result = true;
        } catch (SchedulerException e) {
            result = false;
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除任务
     */
    @Transactional
    public boolean deleteJob(Long id){
        ScheduleTask task =baseMapper.selectById(id);
        Scheduler scheduler = getScheduler();
        JobKey jobKey = JobKey.jobKey(task.getName(), task.getGroup());
        boolean result;
        try{
            scheduler.deleteJob(jobKey);
            // 更新任务状态到数据库
            //task.setStatus(ScheduleTask.STATUS_DELETED);
            //baseMapper.updateStatusById(task.getStatus(), task.getId());
            baseMapper.deleteById(task.getId());
            result = true;
        } catch (SchedulerException e) {
            result = false;
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 立即执行一个任务
     * @param task
     * @throws SchedulerException
     */
    public void startJob(ScheduleTask task) throws SchedulerException{
        Scheduler scheduler = getScheduler();
        JobKey jobKey = JobKey.jobKey(task.getName(), task.getGroup());
        scheduler.triggerJob(jobKey);
    }

    /**
     * 更新任务时间表达式
     * @param task
     * @throws SchedulerException
     */
    @Transactional
    public void updateCron(ScheduleTask task) throws SchedulerException {
        Scheduler scheduler = getScheduler();
        TriggerKey triggerKey = TriggerKey.triggerKey(task.getName(), task.getGroup());
        //获取trigger，即在spring配置文件中定义的 bean id="myTrigger"
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        //表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCron());
        //按新的cronExpression表达式重新构建trigger
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
        //按新的trigger重新设置job执行
        scheduler.rescheduleJob(triggerKey, trigger);

        // 更新 job 信息到数据库
        task.setStartTime(trigger.getStartTime());
        task.setNextTime(trigger.getNextFireTime());
        task.setPreviousTime(trigger.getPreviousFireTime());
        save(task);
        getJobDetail(task).getJobDataMap().put(getJobIdentity(task), task);
    }

    /**
     * 设置job的开始schedule时间
     * @param task
     * @throws SchedulerException
     */
    @Transactional
    public void updateStartTime(ScheduleTask task) throws SchedulerException {
        Scheduler scheduler = getScheduler();
        TriggerKey triggerKey = TriggerKey.triggerKey(task.getName(), task.getGroup());
        //获取trigger，即在spring配置文件中定义的 bean id="myTrigger"
        CronTriggerImpl trigger = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
        trigger.setStartTime(task.getStartTime());
        //按新的trigger重新设置job执行
        scheduler.rescheduleJob(triggerKey, trigger);

        // 更新 job 信息到数据库
        task.setStartTime(trigger.getStartTime());
        task.setNextTime(trigger.getNextFireTime());
        task.setPreviousTime(trigger.getPreviousFireTime());
        baseMapper.updateById(task);
        getJobDetail(task).getJobDataMap().put(getJobIdentity(task), task);
    }

    @Override
    public boolean batchUpdate(String ids, String status) {
        String[] idArray =  ids.split(",");
        for (String id : idArray){
            ScheduleTask task = baseMapper.selectById(id);
            task.setStatus(status);
            switch (status) {
                case ScheduleTask.STATUS_RUNNING:
                    resumeJob(task);
                    break;
                case ScheduleTask.STATUS_NOT_RUNNING:
                    pauseJob(task);
                    break;
            }
        }
        return true;
    }

    private ScheduleTask save(ScheduleTask task){
        if (task != null && task.getId() != null && task.getId() > 0){
            baseMapper.updateById(task);
        }else {
            Long id = baseMapper.insertObject(task);
            task.setId(id);
        }
        return task;
    }

    private Scheduler getScheduler() {
        return schedulerFactoryBean.getScheduler();
    }
}
